#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <asm/uaccess.h>

#define BUF_LEN 100

#include "mychar.h"

//主设备号
int major = 11;
//次设备号
int minor = 0;
//申请数量
int mychar_num = 1;

//自定义字符设备
struct openonce {

	//定义字符设备
	struct cdev mydev;
	//共享资源
	int openflag;  //1表示可打开，0表示关闭
	//自旋锁
	spinlock_t lock;
};

struct openonce gdev;

int mychar_open(struct inode *pnode, struct file *pfile){

	struct openonce * pmychardev = NULL;

	//将自定义字符设备的地址赋值给 private_data 变量
	//这样后续的操作函数都不需要使用全局变量
	pfile->private_data = (void *)container_of(pnode->i_cdev, struct openonce, mydev);
	printk("mychar_open is called \n");


	pmychardev = (struct openonce *)pfile->private_data;


	spin_lock(&pmychardev->lock);
	
	if(pmychardev->openflag){

		pmychardev->openflag = 0;
		spin_unlock(&pmychardev->lock);
		return 0;

	}else{

		spin_unlock(&pmychardev->lock);
		return -1;

	}

}

int mychar_close(struct inode *pnode, struct file *pfile){
	//拿到自定义字符设备的地址
	struct openonce *pmychardev = (struct openonce *)pfile->private_data;
	
	printk("mychar_close is called \n");
	
	spin_lock(&pmychardev->lock);
	pmychardev->openflag = 1;	
	spin_unlock(&pmychardev->lock);

	return 0;
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mychar_open,
	.release = mychar_close,
};

int __init mychar_init(void)
{
	int ret = 0;
	//设备号
	dev_t devno = MKDEV(major, minor);
	//从当前设备号开始验证，mychar_num数量的设备号。若系统没有占用，则申请占用mychar_num数量的设备号
	//"mychar": 该设备号在/proc/devices文件中的名称。意义：确保设备号申请成功，及方便后续据此设备号、名称创建设备文件
	ret = register_chrdev_region(devno, mychar_num, "mychar");

	if(ret){
		//系统动态分配
		ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
		if(ret){
			printk("get devno failed\n");
			return -1;
		}
		//此时设备号是由系统分配的
		major = MAJOR(devno);
	}

	//初始化字符设备,指定所属内核模块，指定操作函数集
	gdev.mydev.owner = THIS_MODULE;
	cdev_init(&gdev.mydev, &myops);

	//将cdev对象添加到内核的字符设备链表中
	cdev_add(&gdev.mydev, devno, mychar_num);

	//初始化自旋锁
	spin_lock_init(&gdev.lock);
	//初始化打开标识
	gdev.openflag = 1;

	return 0;
}

void __exit mychar_exit(void)
{

	dev_t devno = MKDEV(major, minor);

	//从内核的字符设备链表中移除该设备
	cdev_del(&gdev.mydev);

	//释放设备号
	unregister_chrdev_region(devno, mychar_num);

}

MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);
